package com.xflib.tool.qscp;

import cn.hutool.core.util.StrUtil;
import com.xflib.framework.common.ApplicationContext;
import com.xflib.framework.log.StreamGobbler;
import com.xflib.framework.ssh.HostProperties;
import com.xflib.framework.util.*;
import com.xflib.tool.builder.BuilderArgs;
import com.xflib.tool.builder.BuilderFactory;
import com.xflib.tool.builder.BuilderProperties;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

/**
 * @author koradji@163.com
 * @date 2023/10/24
 */
public class App {

    private static Logger logger = LoggerFactory.getLogger(App.class);

    public static YamlUtil yamlUtil;

    public static void main(String[] args) throws Exception {
        ApplicationContext.setTerminated(false);

        AsciiArtUtils.printAsciiArt(System.out, "XFLIB-TOOLS");

        try {
            // 设置 logback.xml 以及 application.yml 路径
            String homePath = ApplicationContext.getApplicationHome(App.class);

            // 配置 logback
            String logbackXml = homePath.concat("logback.xml");
            logbackUtil.init(logbackXml);

            // 配置 动态依赖库
            DynamicJavaLibraryPathUtil.create()
                    .extend(homePath)
                    .extend(homePath.concat(File.separator).concat("lib"))
                    .extend(System.getProperty("user.dir").concat(File.separator).concat("lib"))
                    .build();

            // 处理 命令行参数
            Options options = createOptions();

            // 设置 Header
            CommandLineUtil commandLineUtil = CommandLineUtil.create(options)
                    .setHeader(I18nUtil.getOrDefault("HELP_HEADER", "Developer Helper"));

            // 解析参数
            CommandLine cli = commandLineUtil.parseCommandLine(args);

            boolean okSignal = true;
            // 1. 加密操作
            if (cli.hasOption("e")) {
                String passwordTxt = cli.getOptionValue("e", "");
                if (passwordTxt.length() > 0) {
                    String encryptText = EncryptionAesUtil.create().encrypt(passwordTxt);
                    logger.info("Encrypted: " + encryptText);
                    if (!logger.isInfoEnabled()) {
                        System.out.println("Encrypted: " + encryptText);
                    }
                }
            } else if (cli.hasOption("o") && !cli.hasOption("l")) {
                commandLineUtil.showHelper(options, I18nUtil.getOrDefault(
                        "ERROR_MISSING_OPTION", "Missing argument for option: %s", "-l"));
            } else {
                // 使用指定的 applicattion.yml
                String ymlFile=homePath.concat("application.yml");
                if (okSignal && cli.hasOption("config")) {
                    ymlFile = cli.getOptionValue("config");
                    if (!new File(ymlFile).exists()) {
                        throw new IOException(I18nUtil.getOrDefault("ERROR_FILE_NOTFOUND", "file not found: %s.", ymlFile));
                    }
                }
                yamlUtil = new YamlUtil(ymlFile); // 加载 application.yml 到 YamlUtil
                StreamGobbler.appendWithDebug("====> Application config is using: " + ymlFile,true);
                StreamGobbler.appendWithDebug("====> Log config is using: " + logbackXml,true);

                HostProperties hostProperties = yamlUtil.load(HostProperties.class, "remote");
                hostProperties.setPassword(EncryptionAesUtil.create().decrypt(hostProperties.getPassword())); // 还原密码为明文
                BuilderProperties builderProperties = yamlUtil.load(BuilderProperties.class, "builder");
                BuilderFactory bf = new BuilderFactory(builderProperties, hostProperties);

                // clone
                if (okSignal && cli.hasOption("clone")) {
                    StreamGobbler.appendWithDebug("====> To cloning...",true);
                    clone(cli, bf);
                }

                // branch( switch, tag, delete merged, new )
                if (okSignal && cli.hasOption("branch")) {
                    branch(cli, bf);
                }

                // 1 构建
                if (okSignal && cli.hasOption("b")) {
                    StreamGobbler.appendWithDebug("====> To building...",true);
                    // 验证 构建工具的配置文件, 允许不设置但不允许问价不存在
                    checkMvnConfig(builderProperties);

                    bf.process(new BuilderArgs()
                            .setType(BuilderArgs.PROJECT)
                            .setData(String.join(",", cli.getOptionValues("b")))
                            .setStage(BuilderArgs.STAGE_BUILD)
                    );
                    okSignal = false;
                }
                if (okSignal && cli.hasOption("B")) {
                    // 验证 构建工具的配置文件, 允许不设置但不允许问价不存在
                    checkMvnConfig(builderProperties);

                    bf.process(new BuilderArgs()
                            .setType(BuilderArgs.GROUP)
                            .setData(String.join(",", cli.getOptionValues("B")))
                            .setStage(BuilderArgs.STAGE_BUILD));
                    okSignal = false;
                }

                // 如果 指定了 -l， 优先处理
                if (okSignal && cli.hasOption("l")) {
                    // 3. 文件上传
                    String local = cli.getOptionValue("l", "");
                    if (local.contains("*") || local.contains("?") || local.trim().endsWith(".")) {
                        commandLineUtil.showHelper(options, I18nUtil.getOrDefault("ERROR_WILDCARD_NOT_SUPPORT",
                                "The '.' and wildcard '(*|?)' characters are" +
                                        " not supported with argument value for option -l : %s", local));
                    }
                    String remote = cli.getOptionValue("o", "/tmp/");
                    ScpUtil.create(hostProperties.getHost(), hostProperties.getPort(),
                                    hostProperties.getUsername(), hostProperties.getPassword())
                            //.setBuffer(buffer)
                            .open().exec(local, remote).close();
                }

                // 如果 指定了-c
                if (okSignal && cli.hasOption("c")) {
                    // 2. 远程执行命令
                    String commandLine = cli.getOptionValue("c", "");
                    SshUtil.create(hostProperties.getHost(), hostProperties.getPort(),
                                    hostProperties.getUsername(), hostProperties.getPassword())
                            //.setBuffer(buffer)
                            .open()
                            .exec(commandLine)
                            .close();
                }
            }
        }finally {
            // 延迟退出，确保日志输出正常
            try{Thread.sleep(1000);}catch(InterruptedException ee){Thread.currentThread().interrupt();}

            ApplicationContext.setTerminated(true);
            StreamGobbler.appendWithDebug("====> All has done.",true);
            Runtime.getRuntime().exit(0);
        }
    }

    private static Options createOptions() {
        Options options = new Options();

        options.addOptionGroup(CommandLineUtil.createOptionGroup()
                // 构建项目
                .addOption(Option.builder("b").longOpt("build").hasArgs().valueSeparator(',')
                        .desc(I18nUtil.getOrDefault("HELP_BUILD_PROJECT",
                                "Build project, isolated between multiple projects using ','.")
                        ).build())
                // 构建组
                .addOption(Option.builder("B").longOpt("Build").hasArgs().valueSeparator(',')
                        .desc(I18nUtil.getOrDefault("HELP_BUILD_GROUP",
                                "Build project groups, isolated between multiple groups using ','.")
                        ).build())
                // 密码加密
                .addOption(Option.builder().option("e").longOpt("encrypt").hasArg()
                        .desc(I18nUtil.getOrDefault("HELP_ENCRYPT",
                                "Encrypt password and print result at console.")).build())
                // 帮助
                .addOption(Option.builder("h").longOpt("help")
                        .desc(I18nUtil.getOrDefault("HELP_HELP",
                                "Show help message.")).build())
        );
        options // 执行远程命令
                .addOption(Option.builder("c").longOpt("command").hasArg()
                        .desc(I18nUtil.getOrDefault("HELP_EXEC_CMD",
                                "Execute command at remote.")).build())
                // 指定配置文件
                .addOption(Option.builder().longOpt("config").hasArg()
                        .desc(I18nUtil.getOrDefault("HELP_CONFIG",
                                "Specify configuration file.")).build())
                // 复制本地文件到远程服务器，指定本地目录或文件
                .addOption(Option.builder().option("l").longOpt("local").hasArg()
                        .desc(I18nUtil.getOrDefault("HELP_SCP_L",
                                "Local file path for upload, supports directory or file, the '.' " +
                                "and wildcard characters ('*|?') are not supported. And the * not included in " +
                                "quotation marks Will be converted to the first file or directory matched " +
                                "in the corresponding directory.")).build())
                // 指定远程服务器的存储目录，选项 -l 的扩展项
                .addOption(Option.builder("o").longOpt("remote").hasArg()
                        .desc(I18nUtil.getOrDefault("HELP_SCP_O",
                                "Specify a remote directory and default is /tmp/, " +
                                "must be use in conjunction with option '-l'.")).build())
                // 从远程仓库克隆
                .addOption(Option.builder().longOpt("clone")
                        .desc(I18nUtil.getOrDefault("HELP_CLONE",
                                "Clone a repository and must be used in conjunction with extension item " +
                                        "<-Mgroup=<group1[,...]> or <-Mproject=<project1[,...]>.")).build())
                // 基于分支操作
                .addOption(Option.builder().longOpt("branch").hasArg().optionalArg(true)
                        .desc(I18nUtil.getOrDefault("HELP_BRANCH",
                                "Specified branch when use --switch, --tag, --create.")).build())
                // 切换分支
                .addOption(Option.builder().longOpt("switch").hasArg(false)
                        .desc(I18nUtil.getOrDefault("HELP_BRANCH_SWITCH",
                                "switch branch on repository and must be used in conjunction with --branch and extension item " +
                                        "<-Mgroup=<group1[,...]> or <-Mproject=<project1[,...]>.")).build())
                // 加tag
                .addOption(Option.builder().longOpt("tag").hasArg()
                        .desc(I18nUtil.getOrDefault("HELP_BRANCH_TAG",
                                "create tag on repository and must be used in conjunction with --branch and extension item " +
                                        "<-Mgroup=<group1[,...]> or <-Mproject=<project1[,...]>.")).build())
                // 基于master分支创建新分支
                .addOption(Option.builder().longOpt("create").hasArg(false)
                        .desc(I18nUtil.getOrDefault("HELP_BRANCH_NEW",
                                "Create new branch from master and must be used in conjunction with the extension item " +
                                        "<-Mgroup=<group1[,...]>or<-Mproject=<project1[,..]>.")).build())
                // 删除已合并的全部分支
                .addOption(Option.builder().longOpt("deleteMerged").hasArg(false)
                        .desc(I18nUtil.getOrDefault("HELP_BRANCH_DELETE_MERGED",
                                "Delete merged branches on repository and must be used in conjunction with the extension item " +
                                        "<-Mgroup=<group1[,...]>or<-Mproject=<project1[,..]>.")).build())
                // 扩展选项
                .addOption(Option.builder("M").argName("property=value").hasArgs().valueSeparator('=')
                        .desc(I18nUtil.getOrDefault("HELP_EXTENSION",
                                "Extension option, using ',' to isolate multiple items of a value.")).build())
        ;

        return options;
    }

    private static void checkMvnConfig(BuilderProperties properties) throws FileNotFoundException {
        // 验证 构建工具的配置文件, 允许不设置但不允许问价不存在
        if(StrUtil.isNotBlank(properties.getMvn().getConfigPath()) &&
                ! (new File(properties.getMvn().getConfigPath()).exists())){
            throw new FileNotFoundException(I18nUtil.getOrDefault("ERROR_FILE_NOTFOUND",
                    "The builder config file not found: %s.", properties.getMvn().getConfigPath()));
        }

    }

    private static void clone(CommandLine cli, BuilderFactory bf) {
        Properties more = cli.getOptionProperties("M");
        String groups = more.getProperty("group");
        String projects = more.getProperty("project");
        if (null != projects && !projects.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.PROJECT)
                    .setData(projects)
                    .setStage(BuilderArgs.STAGE_CLONE)
            );
        }
        if (null != groups && !groups.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.GROUP)
                    .setData(groups)
                    .setStage(BuilderArgs.STAGE_CLONE)
            );
        }

    }

    private static void branch(CommandLine cli, BuilderFactory bf){
        String branch = cli.getOptionValue("branch");
        Properties more = cli.getOptionProperties("M");
        String groups = more.getProperty("group");
        String projects = more.getProperty("project");
        String tag = cli.hasOption("tag")?cli.getOptionValue("tag"):"";

        if(cli.hasOption("tag")) {
            StreamGobbler.appendWithDebug("====> To tagging...",true);
            branchOpTag(bf, groups,projects, branch, tag);
        } else if (cli.hasOption("deleteMerged")) {
            StreamGobbler.appendWithDebug("====> To delete merged branch...",true);
            branchOpDeleteMerged(cli, bf);
        } else if (cli.hasOption("create")) {
            StreamGobbler.appendWithDebug("====> To create new branch...",true);
            branchOpNew(cli, bf);
        } else if (cli.hasOption("switch")) {
            StreamGobbler.appendWithDebug("====> To switch branch...",true);
            branchOpSwitch(bf, groups,projects, branch, tag);
        }
    }
    private static void branchOpTag(BuilderFactory bf, String groups, String projects, String branch, String tag){
        if (null != projects && !projects.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.PROJECT)
                    .setData(projects)
                    .setStage(BuilderArgs.STAGE_BRANCH_TAG)
                    .setAttachData("?branch=" + branch + "&tag=" + tag)
            );
        }
        if (null != groups && !groups.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.GROUP)
                    .setData(groups)
                    .setStage(BuilderArgs.STAGE_BRANCH_TAG)
                    .setAttachData("?branch=" + branch + "&tag=" + tag)
            );
        }
    }
    private static void branchOpSwitch(BuilderFactory bf, String groups, String projects, String branch, String tag) {
        if (null != projects && !projects.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.PROJECT)
                    .setData(projects)
                    .setStage(BuilderArgs.STAGE_BRANCH_SWITCH)
                    .setAttachData(branch)
            );
        }
        if (null != groups && !groups.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.GROUP)
                    .setData(groups)
                    .setStage(BuilderArgs.STAGE_BRANCH_SWITCH)
                    .setAttachData(branch)
            );
        }
    }
    private static void branchOpNew(CommandLine cli, BuilderFactory bf){
        String branch = cli.getOptionValue("branch");
        Properties more = cli.getOptionProperties("M");
        String groups = more.getProperty("group");
        String projects = more.getProperty("project");
        if (null != projects && !projects.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.PROJECT)
                    .setData(projects)
                    .setStage(BuilderArgs.STAGE_BRANCH_NEW)
                    .setAttachData(branch)
            );
        }
        if (null != groups && !groups.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.GROUP)
                    .setData(groups)
                    .setStage(BuilderArgs.STAGE_BRANCH_NEW)
                    .setAttachData(branch)
            );
        }

    }
    private static void branchOpDeleteMerged(CommandLine cli, BuilderFactory bf) {
        Properties more = cli.getOptionProperties("M");
        String groups = more.getProperty("group");
        String projects = more.getProperty("project");
        if (null != projects && !projects.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.PROJECT)
                    .setData(projects)
                    .setStage(BuilderArgs.STAGE_BRANCH_DELETE_MERGED)
            );
        }
        if (null != groups && !groups.isEmpty()) {
            bf.process(new BuilderArgs()
                    .setType(BuilderArgs.GROUP)
                    .setData(groups)
                    .setStage(BuilderArgs.STAGE_BRANCH_DELETE_MERGED)
            );
        }

    }


}
